#include <windows.h>
#include <stdio.h>
#include "png.h"
#include "api.h"
#include "image.h"
#include "zlib.h"

#pragma pack(1)

BYTE READ_PNG_SIGNATURE[] = {137, 80, 78, 71, 13, 10, 26, 10, 0};

typedef struct _PNGREADERHANDLER_{
	int dx, dy;
	int line;
	PBYTE ptr;
	FILE *fpng;
	PBYTE raw;
	PBYTE last;
} *PPNGREADERHANDLER, PNGREADERHANDLER;


int pngReaderSignature(FILE *fpng){
	char sig[8];
	int readed = fread(sig, 8, 1, fpng);
	return !strncmp(sig, (char*)READ_PNG_SIGNATURE, 8);
}

int pngReaderDataNumber(FILE *fpng, int size){
	long value = 0;
	char *ptr = (char*)&value;
	ptr+=size;
	for(int i=0;i<size;i++){
		ptr--;
		fread(ptr, 1,1, fpng);
	}
	return value;
}

int pngReaderChunkDword(FILE *fpng){
	long value;
	fread(&value, 1, 4, fpng);
	return value;
}

void pngReaderChunkData(FILE *fpng, int len){
	fseek(fpng, len, 1);
}

void pngReaderChunkIHDR(FILE *fpng, PPNGHEADER header){
	header->width = pngReaderDataNumber(fpng, 4);
	header->height = pngReaderDataNumber(fpng, 4);
	fread(&header->bitdepth, 5, 1, fpng);
	pngReaderChunkDword(fpng);	// Skip CRC
}

PPNGREADERHANDLER pngReaderCreateHandler(){
	NEW_VALUE(PNGREADERHANDLER, handler);
	return handler;
}

void pngReaderLoadHeader(PPNGREADERHANDLER handler, PIMAGEHEADER header){
	pngReaderSignature(handler->fpng);
	PNGHEADER pngHeader;
	int len;
	int id;
	int crc;
	while(!feof(handler->fpng) && id!=ID_IDHR){
		len = pngReaderDataNumber(handler->fpng, 4);
		id = pngReaderChunkDword(handler->fpng);
		if (id!=ID_IDHR){
			pngReaderChunkData(handler->fpng, len);
			crc = pngReaderChunkDword(handler->fpng);
		}		
	}
	if (id==ID_IDHR){
		pngReaderChunkIHDR(handler->fpng, &pngHeader);
		header->bits = pngHeader.bitdepth;
		header->dx = pngHeader.width;
		header->dy = pngHeader.height;
		handler->dx = pngHeader.width;
		handler->dy = pngHeader.height;
		handler->line = (handler->dx*3)+1;
		header->flg = IMAGE_FLG_UPTODOWN;
		if (pngHeader.bitdepth!=8 || pngHeader.colorType!=2){
			header->error = TRUE;	// Currently dont have plans to support anothe types than 24b without Alpha
		}
	}
	else{
		header->error = TRUE;
	}
}

void pngReaderInflate(BYTE *in, int lenIn, BYTE *out, int lenOut){
	z_stream strm;
	int ret;
	memset(&strm, 0, sizeof(strm));
	ret = inflateInit(&strm);
	if (ret != Z_OK)
        return;
	strm.avail_in = lenIn;
	strm.next_in = in;
	strm.avail_out = lenOut;
	strm.next_out = out;
	ret = inflate(&strm, Z_NO_FLUSH);
	inflateEnd(&strm);
}

void pngReaderFilterSub(BYTE *in, int line){
	int i;
	BYTE prior;
	for(i=0;i<line;i++){
		if (i<3){
			prior = 0;
		}
		else{
			prior = in[i-3];
		}
		in[i] += prior;
	}
}

void pngReaderFilterUp(BYTE *pre, BYTE *in, int line){
	int i;
	for(i=0;i<line;i++){		
		in[i] += pre[i];
	}
}

void pngReaderFilterAverage(BYTE *pre, BYTE *in, int line){
	int i;
	BYTE a, b;
	for(i=0;i<line;i++){
		a = i<3?0:in[i-3];
		b = pre?pre[i]:0;
		in[i] += (a+b)/2;
	}
}

BYTE pngReaderPaeth(int a, int b, int c){
	int p = a+b-c;
	int pa = abs(p-a);
	int pb = abs(p-b);
	int pc = abs(p-c);
	if (pa <= pb && pa <= pc){
		return a;
	}
	else if (pb <= pc){
		return b;
	}
	else{
		return c;
	}
	return 0;
}

void pngReaderFilterPaeth(BYTE *pre, BYTE *in, int line){
	int i;
	for(i=0;i<line;i++){
		in[i] += pngReaderPaeth(i<3?0:in[i-3], pre?pre[i]:0, (i>=3 && pre)?pre[i-3]:0);
	}
}

void pngReaderFilterRawData(PPNGREADERHANDLER handler){
	BYTE *source = handler->raw;
	int line = handler->line;
	int i;
	for(i=0;i<handler->dy;i++){
		switch(*source){
		case 1:pngReaderFilterSub(source+1, line);
			break;
		case 2:pngReaderFilterUp(source-line, source+1,line);
			break;
		case 3:pngReaderFilterAverage(source-line, source+1,line);
			break;
		case 4:pngReaderFilterPaeth(i?source-line:NULL, source+1,line);
			break;
		}
		source += line+1;
	}	
}

void pngReaderLoadData(PPNGREADERHANDLER handler){
	int len = 0;
	int rawLen = 0;
	handler->raw = (PBYTE)malloc(100);
	int id = 0;
	int crc = 0;
	while(!feof(handler->fpng) && id!=ID_IEND){
		len = pngReaderDataNumber(handler->fpng, 4);
		id = pngReaderChunkDword(handler->fpng);
		if (id==ID_IDAT){			
			handler->raw = (PBYTE)realloc(handler->raw, rawLen + len);
			fread(handler->raw + rawLen, len, 1, handler->fpng);
			crc = pngReaderChunkDword(handler->fpng);
			rawLen += len;
		}else{
			pngReaderChunkData(handler->fpng, len);
			crc = pngReaderChunkDword(handler->fpng);
		}
	}
	int total = handler->line*handler->dy;
	PBYTE map = (PBYTE)malloc(total);
	pngReaderInflate(handler->raw, rawLen, map, total);
	free(handler->raw);
	handler->raw = map;
	handler->ptr = map;
}

void pngReaderScanLine(PPNGREADERHANDLER handler, PBYTE output){
	PBYTE input = handler->ptr+1;	// Skip the line filter
	int i;
	for(i=0;i<handler->dx;i++){
		output[0] = input[2];
		output[1] = input[1];
		output[2] = input[0];
		input += 3;
		output += 3;
	}
	handler->ptr += handler->line;
}

void pngReaderDeleteHandler(PPNGREADERHANDLER handler){
	if (handler->raw){
		free(handler->raw);
	}
	free(handler);
}

DWORD pngReader(UINT command, PVOID handler, PVOID param){
	PPNGREADERHANDLER data = (PPNGREADERHANDLER)handler;
	switch(command){
	case IMAGE_LOADER_CREATEHANDLER:
		return (DWORD)pngReaderCreateHandler();
	case IMAGE_LOADER_OPENIMAGE:
		data->fpng = fopen((char*)param, "rb+");
		break;
	case IMAGE_LOADER_LOADHEADER:
		pngReaderLoadHeader(data, (PIMAGEHEADER)param);
		pngReaderLoadData(data);
		pngReaderFilterRawData(data);
		break;
	case IMAGE_LOADER_SCANLINE:
		pngReaderScanLine(data, (PBYTE)param);
		break;
	case IMAGE_LOADER_CLOSEIMAGE:
		fclose(data->fpng);
		break;
	case IMAGE_LOADER_DELETEHANDLER:
		pngReaderDeleteHandler(data);
		break;
	}
	return 0;
}